package internal.reflect;

import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.lightcommons.json.JSONArray;
import org.lightcommons.json.JSONObject;
import org.lightcommons.logger.Logger;
import org.lightcommons.util.ConvertUtils;
import org.lightcommons.util.ReflectionUtils;
import org.lightmtv.config.MtvConvert;
import org.lightmtv.config.TypeConverter;

public abstract class AbstractTypeConverter implements TypeConverter{
	
	private List<MtvConvert> mtvConverts;
	
	public void add(MtvConvert convert){
		mtvConverts.add(convert);
	}
	
	public AbstractTypeConverter(List<MtvConvert> mtvConverts) {
		super();
		this.mtvConverts = mtvConverts;
	}

	@SuppressWarnings("unchecked")
	public <T> T convertTo(Object value, Class<T> toType) {
		return (T) convert(value, toType);
	}
	
	@SuppressWarnings("unchecked")
	protected Object convertFromString(String value, Type toType) {
		
		if (value == null)
			return null;
		try {
			Class toClass=null;
			Type[] paramTypes=null;
			
			if(toType instanceof Class){
				toClass=(Class) toType;
			}else if(toType instanceof ParameterizedType){
				toClass=(Class) ((ParameterizedType)toType).getRawType();
				paramTypes = ((ParameterizedType)toType).getActualTypeArguments();
			}
			
			for(MtvConvert convert: mtvConverts){
				if(convert.support(toClass)){
					return convert.convertFromString(value, toClass);
				}
			}
			
			//=== null handler
			if("null".equals(value) && !toClass.isAssignableFrom(String.class)){
				return null;
			}
			//======= here is the cast
			if (toClass.isArray()) {
				Class componentType = toClass.getComponentType();
				JSONArray ja = JSON.toJSONArray(value);
				if(ja==null && componentType.equals(byte.class)){
					return value.getBytes();
				}
				Object array = Array.newInstance(componentType, ja
						.length());
				for (int i = 0; i < ja.length(); i++) {
					Array.set(array, i, get(ja, i, componentType));
				}
				return array;
			} else if (Collection.class.isAssignableFrom(toClass)) {
				Type comType=Object.class;
				if(paramTypes!=null && paramTypes.length==1){
					comType= paramTypes[0];
				}
				JSONArray ja = JSON.toJSONArray(value);
				Collection c = (Collection)ReflectionUtils.newInstance(toClass);
				for(int i=0;i<ja.length();i++){
					c.add(get(ja, i, comType));
				}
				return c;
			} else if(Map.class.isAssignableFrom(toClass)){
				JSONObject jo = new JSONObject(value);
				Map m = (Map) ReflectionUtils.newInstance(toClass);
				if(paramTypes!=null && paramTypes.length==2){
					for(Iterator i=jo.keys();i.hasNext();){
						String k = (String) i.next();
						String v = jo.getString(k);
						m.put(convertFromString(k, (Class)paramTypes[0]), convertFromString(v, (Class)paramTypes[1]));
					}
				}else{
					for(Iterator i=jo.keys();i.hasNext();){
						String k = (String) i.next();
						Object v = jo.get(k);
						m.put(k,v);
					}
				}
				return m;
			} else if (Enum.class.isAssignableFrom(toClass)){
				try{
					return Enum.valueOf(toClass, value);
				}catch(Throwable t){
					try{
						int index=Integer.valueOf(value);
						Object values = toClass.getMethod("values").invoke(null);
						return Array.get(values, index);
					}catch(Throwable tt){
					}
				}
			} else{
				return ConvertUtils.convert(value, toClass);
			}
		} catch (Throwable e) {
			Logger.getLogger(getClass()).debug(e);
		}
		return null;
	}

	private Object get(JSONArray ja, int index, Type toType) {
		try {
			if (toType.equals(Object.class)) {
				return ja.get(index);
			} else {
				return convertFromString(ja.getString(index), toType);
			}
		} catch (Throwable t) {

		}
		return null;
	}

	@SuppressWarnings("unchecked")
	public Object convert(Object value, Type toType) {
		if(value==null){
			return null;
		}
		Class toClass = ReflectionUtils.getRawClass(toType);
		if(toClass.isAssignableFrom(value.getClass())){
			return value;
		}
		if(value instanceof String){
			Object ret = convertFromString((String) value, toType);
			if(ret!=null)
				return ret;
		}
		return convertFromAnyInternal(value,toClass);
	}

	@SuppressWarnings("unchecked")
	private Object convertFromAnyInternal(Object value, Class toClass){
		for(MtvConvert convert: mtvConverts){
			if(convert.support(toClass)){
				return convert.convertFromAny(value, toClass);
			}
		}
		return convertFromAny(value, toClass);
	}

	protected abstract Object convertFromAny(Object value, Class toClass) ;

}
